package xclient

import (
	"errors"
	"math"
	"math/rand"
	"sync"
	"time"
)

// SelectMode 服务选取模式
type SelectMode int

const (
	RandomSelect    SelectMode = iota // 随机策略
	RondRobinSelect                   // 轮询策略
)

// Discovery 服务发现
type Discovery interface {
	Refresh() error                      // 从注册中心中更新服务列表
	Update(servers []string) error       // 手动更新服务列表
	Get(mode SelectMode) (string, error) // 根据负载均衡策略，获取一个服务实例
	GetAll() ([]string, error)           // 返回所有服务实例
}

var _ Discovery = (*MultiServersDiscovery)(nil)

// MultiServersDiscovery 是一个针对多台服务器的发现，无需注册中心用户显式地提供服务器地址
type MultiServersDiscovery struct {
	r       *rand.Rand   // 生成一个随机数
	mu      sync.RWMutex // 读写锁
	servers []string     // 服务列表
	index   int          // 记录robin算法选择的位置
}

// NewMultiServersDiscovery 实例化
func NewMultiServersDiscovery(servers []string) *MultiServersDiscovery {
	d := &MultiServersDiscovery{
		r:       rand.New(rand.NewSource(time.Now().UnixNano())),
		servers: servers,
	}
	// index 记录 Round Robin 算法已经轮询到的位置，为了避免每次从 0 开始，初始化时随机设定一个值。
	d.index = d.r.Intn(math.MaxInt32 - 1)
	return d
}

// Refresh 刷新对MultiServersDiscovery没有意义，所以忽略它
func (d *MultiServersDiscovery) Refresh() error {
	return nil
}

// Update 如果需要，动态更新发现服务器
func (d *MultiServersDiscovery) Update(servers []string) error {
	d.mu.Lock()
	defer d.mu.Unlock()
	d.servers = servers
	return nil
}

// Get 获取某个服务实例
func (d *MultiServersDiscovery) Get(mode SelectMode) (string, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	n := len(d.servers)
	if n == 0 {
		return "", errors.New("rpc discovery: no available servers")
	}
	switch mode {
	case RandomSelect:
		return d.servers[d.r.Intn(n)], nil
	case RondRobinSelect:
		s := d.servers[d.index%n] // 服务器可以更新，因此模式n以确保安全
		d.index = (d.index + 1) % n
		return s, nil
	default:
		return "", errors.New("rpc discovery: not supported select mode")
	}
}

// GetAll 获取所有服务实例
func (d *MultiServersDiscovery) GetAll() ([]string, error) {
	d.mu.Lock()
	defer d.mu.Unlock()
	// 返回d.servers的副本
	servers := make([]string, len(d.servers), len(d.servers))
	copy(servers, d.servers)
	return servers, nil
}
